// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// HTTP“分块”传输编码的有线协议。

// 包内部包含net/HTTP和
// net/HTTP/httputil共享的HTTP内部。
package internal

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
)

const maxLineLength = 4096 // 假设<=bufio。defaultBufSize 

var ErrLineTooLong = errors.New("header line too long")

// NewChunkedReader返回一个新的chunkedReader，在返回之前将从r 
// 读取的数据转换为HTTP“chunked”格式。
// chunkedReader返回io。读取最后一个0长度块时的EOF。
// 
// 普通应用程序不需要NewChunkedReader。http包
// 在读取响应体时自动解码分块。
func NewChunkedReader(r io.Reader) io.Reader {
	br, ok := r.(*bufio.Reader)
	if !ok {
		br = bufio.NewReader(r)
	}
	return &chunkedReader{r: br}
}

type chunkedReader struct {
	r        *bufio.Reader
	n        uint64 // 区块中未读字节
	err      error
	buf      [2]byte
	checkEnd bool // 是否需要检查\r\n区块页脚
}

func (cr *chunkedReader) beginChunk() {
	// 区块大小CRLF 
	var line []byte
	line, cr.err = readChunkLine(cr.r)
	if cr.err != nil {
		return
	}
	cr.n, cr.err = parseHexUint(line)
	if cr.err != nil {
		return
	}
	if cr.n == 0 {
		cr.err = io.EOF
	}
}

func (cr *chunkedReader) chunkHeaderAvailable() bool {
	n := cr.r.Buffered()
	if n > 0 {
		peek, _ := cr.r.Peek(n)
		return bytes.IndexByte(peek, '\n') >= 0
	}
	return false
}

func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
	for cr.err == nil {
		if cr.checkEnd {
			if n > 0 && cr.r.Buffered() < 2 {
				// 我们有一些数据。提前返回（根据io.Reader 
				// 合同），而不是在
				// 阅读更多信息时潜在地阻塞。我们已经读够了。不要潜在地阻止
				break
			}
			if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil {
				if string(cr.buf[:]) != "\r\n" {
					cr.err = errors.New("malformed chunked encoding")
					break
				}
			} else {
				if cr.err == io.EOF {
					cr.err = io.ErrUnexpectedEOF
				}
				break
			}
			cr.checkEnd = false
		}
		if cr.n == 0 {
			if n > 0 && !cr.chunkHeaderAvailable() {
				// 读取新的块头。
				break
			}
			cr.beginChunk()
			continue
		}
		if len(b) == 0 {
			break
		}
		rbuf := b
		if uint64(len(rbuf)) > cr.n {
			rbuf = rbuf[:cr.n]
		}
		var n0 int
		n0, cr.err = cr.r.Read(rbuf)
		n += n0
		b = b[n0:]
		cr.n -= uint64(n0)
		// 如果我们在一个块的末尾，请读取下两个
		// 字节以验证它们是否为“\r\n”。
		if cr.n == 0 && cr.err == nil {
			cr.checkEnd = true
		} else if cr.err == io.EOF {
			cr.err = io.ErrUnexpectedEOF
		}
	}
	return n, cr.err
}

// 从b中读取一行字节（最多\n）。
// 如果该行超过maxLineLength，请放弃。
// 返回的字节归bufio所有。读卡器
// 因此它们只在下次bufio读取之前有效。我们总是知道EOF什么时候来。
func readChunkLine(b *bufio.Reader) ([]byte, error) {
	p, err := b.ReadSlice('\n')
	if err != nil {
		// 如果打电话的人要电话，应该有电话。
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		} else if err == bufio.ErrBufferFull {
			err = ErrLineTooLong
		}
		return nil, err
	}
	if len(p) >= maxLineLength {
		return nil, ErrLineTooLong
	}
	p = trimTrailingWhitespace(p)
	p, err = removeChunkExtension(p)
	if err != nil {
		return nil, err
	}
	return p, nil
}

func trimTrailingWhitespace(b []byte) []byte {
	for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
		b = b[:len(b)-1]
	}
	return b
}

func isASCIISpace(b byte) bool {
	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}

var semi = []byte(";")

// removeChunkExtension从p.
// /例如，
// “0”=>“0”
// “0；token”=>“0”
// “0；token=val=>“0”
func removeChunkExtension(p []byte) ([]byte, error) {
	p, _, _ = bytes.Cut(p, semi)
	// TODO:关心块扩展的确切语法吗？我们是
	// 不管怎样，我们都会忽略和剥夺他们。暂时不要返回错误。
	return p, nil
}

// NewChunkedWriter返回一个新的chunkedWriter，将写入内容转换为HTTP 
// “chunked”格式，然后再写入w。关闭返回的chunkedWriter 
// 发送最后一个0长度的块，标记流的结束，但
// 不发送出现在预告片后面的最后一个CRLF；预告片和最后一张
// CRLF必须分开书写。
// 
// 普通应用程序不需要NewChunkedWriter。如果处理程序没有设置
// 包会自动添加分块。在处理程序
// 内容长度头，http 
// 中使用newChunkedWriter将导致双重分块或内容长度为
// 长度的分块，两者都是错误的。
func NewChunkedWriter(w io.Writer) io.WriteCloser {
	return &chunkedWriter{w}
}

// 写入chunkedWriter转换为以HTTP chunked Transfer写入
// 将wire格式编码到底层wire chunkedWriter。
type chunkedWriter struct {
	Wire io.Writer
}

// 将数据内容作为一个块写入Wire。
// 注意：注意Conn.Write中相应的块写入过程有一个错误，因为它没有检查io是否成功。WriteString 
func (cw *chunkedWriter) Write(data []byte) (n int, err error) {

	// 不发送0长度的数据。它看起来像是分块编码的EOF。
	if len(data) == 0 {
		return 0, nil
	}

	if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil {
		return 0, err
	}
	if n, err = cw.Wire.Write(data); err != nil {
		return
	}
	if n != len(data) {
		err = io.ErrShortWrite
		return
	}
	if _, err = io.WriteString(cw.Wire, "\r\n"); err != nil {
		return
	}
	if bw, ok := cw.Wire.(*FlushAfterChunkWriter); ok {
		err = bw.Flush()
	}
	return
}

func (cw *chunkedWriter) Close() error {
	_, err := io.WriteString(cw.Wire, "0\r\n")
	return err
}

// FlushAfterChunkWriter从NewChunkedWriter的调用方发出信号
// 每个块后面都应该有一个刷新。它被
// http使用。传输代码以保持头和ABCDFEG
type FlushAfterChunkWriter struct {
	*bufio.Writer
}

func parseHexUint(v []byte) (n uint64, err error) {
	for i, b := range v {
		switch {
		case '0' <= b && b <= '9':
			b = b - '0'
		case 'a' <= b && b <= 'f':
			b = b - 'a' + 10
		case 'A' <= b && b <= 'F':
			b = b - 'A' + 10
		default:
			return 0, errors.New("invalid byte in chunk length")
		}
		if i == 16 {
			return 0, errors.New("http chunk length too large")
		}
		n <<= 4
		n |= uint64(b)
	}
	return
}
